﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Data;

namespace QRCodeCreator
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window, System.ComponentModel.INotifyPropertyChanged
    {
        #region INotifyPropertyChanged成员

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName = "")
        {
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }

        #endregion

        // TODO 一个超长的纯数字, 能生成二维码, 但无法正常解释
        // 例如 23921238123912837198273891273987182379812893718923789127389179832798123891232738917983279812389123

        ZXing.BarcodeWriter mBarcodeWriter { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            this.Title = $"二维码生成器 - V {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()}";

            this.DataContext = this;

            mBarcodeWriter = new ZXing.BarcodeWriter();

            ZXing.QrCode.QrCodeEncodingOptions options = new ZXing.QrCode.QrCodeEncodingOptions
            {
                DisableECI = true,
                CharacterSet = "UTF-8",
                //Width = 870,
                Width = 1840,
                //Height = 870,
                Height = 1840,
                ErrorCorrection = ZXing.QrCode.Internal.ErrorCorrectionLevel.L,
                PureBarcode = false,
            };

            // options.QrCompact = false; // TODO 未知
            // options.Hints.Add(ZXing.EncodeHintType.)            

            // options.CharacterSet = "0123456789"; // Bartender - 数字的
            // options.CharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // Bartender - 字母数字的
            // options.CharacterSet = "Shift_JIS"; // Bartender - 日本汉字

            mBarcodeWriter.Format = ZXing.BarcodeFormat.QR_CODE;
            mBarcodeWriter.Options = options;

            initEvent();

            this.NetworkInterfaceAdvList = Howe.Helper.WinNetworkHelper.GetAllDevice();

            this.txtQRCodeContent.AppendText("TEST");
        }

        private void initEvent()
        {
            // UI
            this.txtQRCodeContent.SizeChanged += txtQRCodeContent_SizeChanged;
            this.txtDecodeContent.SizeChanged += txtDecodeContent_SizeChanged;
            this.Closed += new EventHandler(MainWindow_Closed);
            this.img.MouseDown += new MouseButtonEventHandler(img_MouseDown);

            this.Activated += MainWindow_Activated;

            this.ucsfExcelFile.SuccessEventHandler += new EventHandler<EventArgs>(excelFile_ImportSuccess);

            // BusinessLogic
            this.txtQRCodeContent.TextChanged += txtQRCodeContent_TextChanged;
            this.cbErrorCorrectionLevel.SelectionChanged += (o, e) => { optionChanged(); };
            this.cbCharacterSet.SelectionChanged += (o, e) => { optionChanged(); };
            this.cbQR_Version.SelectionChanged += (o, e) => { optionChanged(); };
            this.cbQR_Mask.SelectionChanged += (o, e) => { optionChanged(); };
            this.cb_Margin.SelectionChanged += (o, e) => { optionChanged(); };

            this.btnDecode.Click += BtnDecode_Click;

            txtDecodeContent.MouseDoubleClick += TxtDecodeContent_MouseDoubleClick;

            // 自定义 PopUp 控件位置
            // https://docs.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-specify-a-custom-popup-position?view=netframeworkdesktop-4.8
            popup0.CustomPopupPlacementCallback = new System.Windows.Controls.Primitives.CustomPopupPlacementCallback(placePopup);

            // Command
            CMD_FromClipboard = new WPFControls.Command(FromClipboard);
            CMD_FromClipboard2 = new WPFControls.Command(FromClipboard2);

            CMD_MyTcpServerSend = new WPFControls.Command(CMD_Method_MyTcpServerSend);
        }


        public WPFControls.Command CMD_MyTcpServerSend { get; set; }

        public WPFControls.Command CMD_FromClipboard { get; set; }

        public WPFControls.Command CMD_FromClipboard2 { get; set; }

        #region UI 事件

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            this.txtQRCodeContent.Focus();
            this.txtQRCodeContent.SelectAll();
        }

        void MainWindow_Closed(object sender, EventArgs e)
        {
            this.img.Source = null;
            closeAllFrmViewImage();

            // 关闭 TcpServer 
            if (mMyTcpServer?.IsServerStart == true)
            {
                mMyTcpServer?.StopServer();
                mMyTcpServer.StatusChange -= new EventHandler<Util.Web.TcpXxxStatusChangeEventArgs>(myTcpServer_StatusChange_Handle);
                mMyTcpServer = null;
            }

            // 关闭监听窗口
            if (frmTcpServerConsole != null)
            {
                frmTcpServerConsole.IsShutdownApplication = true;
                frmTcpServerConsole.Close();
            }
        }

        void txtQRCodeContent_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            // this.Height = 462 + e.NewSize.Height - 21.84;
            this.Height = 500 + e.NewSize.Height + txtDecodeContent.ActualHeight;
        }

        void txtDecodeContent_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.Height = 500 + txtQRCodeContent.ActualHeight + e.NewSize.Height;
        }

        Util.ActionUtils.DebounceAction mDebounceAction = new Util.ActionUtils.DebounceAction();

        void txtQRCodeContent_TextChanged(object sender, TextChangedEventArgs e)
        {
            mDebounceAction.Debounce(1000, drawQRCode, this.Dispatcher);
        }

        #endregion

        /// <summary>
        /// 获取 RichTextbox 信息
        /// </summary>
        /// <param name="rtb"></param>
        /// <returns></returns>
        private string getDocumentStringFormRichTextBox(RichTextBox rtb)
        {
            TextRange textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
            string r = textRange.Text;

            // 减少系统抛错
            if (r.Length > 2) // 由于 RichTextBox 自带\r\n 故去掉2位
            {
                r = r.Substring(0, r.Length - 2);
            }

            return r;
        }

        #region 下拉框选项更改后, 获取最新 Writer 配置

        private void optionChanged()
        {
            ZXing.QrCode.QrCodeEncodingOptions options = new ZXing.QrCode.QrCodeEncodingOptions
            {
                DisableECI = true,
                // Width = 870,
                Width = 1840,
                // Height = 870,
                Height = 1840,
            };

            this.setOptions(options);

            mBarcodeWriter.Options = options;

            drawQRCode();
        }

        /// <summary>
        /// 可以参考 bartender 10.1 中二维码的配置
        /// </summary>
        /// <param name="options"></param>
        void setOptions(ZXing.QrCode.QrCodeEncodingOptions options)
        {
            #region 纠错等级

            ZXing.QrCode.Internal.ErrorCorrectionLevel ecl = ZXing.QrCode.Internal.ErrorCorrectionLevel.L;
            if (cbErrorCorrectionLevel.SelectedValue is ComboBoxItem cbi1)
            {
                switch (cbi1.Tag.ToString())
                {
                    case "L":
                        ecl = ZXing.QrCode.Internal.ErrorCorrectionLevel.L; break;
                    case "M":
                        ecl = ZXing.QrCode.Internal.ErrorCorrectionLevel.M; break;
                    case "Q":
                        ecl = ZXing.QrCode.Internal.ErrorCorrectionLevel.Q; break;
                    case "H":
                        ecl = ZXing.QrCode.Internal.ErrorCorrectionLevel.H; break;
                }
            }

            options.ErrorCorrection = ecl;

            #endregion

            #region 文本编码

            string characterSet = "UTF-8";
            if (cbCharacterSet.SelectedValue is ComboBoxItem cbi2)
            {
                string tempEncoding = cbi2.Content.ToString();
                try
                {
                    characterSet = ZXing.Common.CharacterSetECI.getCharacterSetECIByName(tempEncoding).EncodingName;

                    #region 知识点 ZXing.Common.CharacterSetECI

                    // ZXing.Common.CharacterSetECI.getCharacterSetECIByName()
                    // 从网页 https://zxing.github.io/zxing/apidocs/com/google/zxing/common/CharacterSetECI.html#UnicodeBigUnmarked
                    // 了解到 zxing 只支持

                    //ASCII
                    //Big5
                    //Cp1250
                    //Cp1251
                    //Cp1252
                    //Cp1256
                    //Cp437
                    //EUC_KR
                    //GB18030
                    //ISO8859_1
                    //ISO8859_10
                    //ISO8859_11
                    //ISO8859_13
                    //ISO8859_14
                    //ISO8859_15
                    //ISO8859_16
                    //ISO8859_2
                    //ISO8859_3
                    //ISO8859_4
                    //ISO8859_5
                    //ISO8859_6
                    //ISO8859_7
                    //ISO8859_8
                    //ISO8859_9
                    //SJIS
                    //UnicodeBigUnmarked
                    //UTF8

                    #endregion
                }
                catch (Exception ex)
                {
                    WPFControls.MessageBox.ShowErrorDialog($"{ex.Message}\r\n编码{tempEncoding}不存在");
                }
            }

            options.CharacterSet = characterSet;

            #endregion

            #region 符号与版本

            string qrcodeVersion = "自动";
            if (cbQR_Version.SelectedValue is ComboBoxItem cbi3)
            {
                qrcodeVersion = cbi3.Content.ToString();
            }

            if (qrcodeVersion != "自动")
            {
                int qrVersion = int.Parse(qrcodeVersion.Split('(')[0].Trim());
                options.QrVersion = qrVersion;
                // options.Hints.Add(ZXing.EncodeHintType.QR_VERSION, qrVersion); // 也可以在 Hints 中配置
            }

            #endregion

            #region 掩码

            string qrcodeMask = "自动";
            if (cbQR_Mask.SelectedValue is ComboBoxItem cbi4)
            {
                qrcodeMask = cbi4.Content.ToString();
            }

            if (qrcodeMask != "自动")
            {
                int qrMask = int.Parse(qrcodeMask);
                options.Hints.Add(ZXing.EncodeHintType.QR_MASK_PATTERN, qrMask);
            }

            #endregion

            #region Margin

            string strMargin = "自动";
            if (cb_Margin.SelectedValue is ComboBoxItem cbi5)
            {
                strMargin = cbi5.Content.ToString();
            }

            if (strMargin != "自动")
            {
                int margin = int.Parse(strMargin);
                options.Hints.Add(ZXing.EncodeHintType.MARGIN, margin);
            }

            // 还有一个配置可以设置无边框
            // options.Hints.Add(ZXing.EncodeHintType.NO_PADDING, true); // 没有边框

            #endregion

            // 有待学习
            // 1. GS1_FORMAT
            // options.Hints.Add(ZXing.EncodeHintType.GS1_FORMAT, true); // bartender 10.1 的 开始模式中有一项 GS1(FNC1) 不知道是否有关系

            // 2. PureBarcode
            // Don't put the content string into the output image.
            // Output only barcode, no Human Readable Interpretation.
            // options.PureBarcode = false; 

        }

        #endregion

        #region 绘制二维码

        private void drawQRCode()
        {
            this.Dispatcher.Invoke(new Action(() =>
            {
                string content = this.getDocumentStringFormRichTextBox(this.txtQRCodeContent);
                this.img.Source = zxingDrawQRCode(content);
            }));
        }

        private BitmapSource zxingDrawQRCode(string content)
        {
            if (string.IsNullOrWhiteSpace(content) == true)
            {
                return null;
            }
            else
            {
                // 绘制出来的 bitmap 默认dpi 96
                System.Drawing.Bitmap bitmap = mBarcodeWriter.Write(content);
                BitmapSource r = bitmap2BitmapImage(bitmap);
                bitmap.Dispose();
                return r;
            }
        }

        private BitmapSource bitmap2BitmapImage(System.Drawing.Bitmap bitmap)
        {
            BitmapImage r = new BitmapImage();
            r.BeginInit();
            r.CacheOption = BitmapCacheOption.OnLoad;
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            r.StreamSource = ms;
            r.EndInit();

            return r;
        }

        #endregion

        #region 生成窗口 ( 1秒内对准图片控件点击左键2次, 生成窗口 )

        private List<FrmViewImage> mFrmViewImageList = new List<FrmViewImage>();

        private DateTime? mMouseDownDateTime = null;

        void img_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                if (mMouseDownDateTime.HasValue == true)
                {
                    DateTime now = DateTime.Now;
                    double value = TimeSpan.FromTicks(now.Ticks).TotalMilliseconds - TimeSpan.FromTicks(mMouseDownDateTime.Value.Ticks).TotalMilliseconds;
                    if (value.CompareTo(1000) < 0)
                    {
                        mMouseDownDateTime = null;

                        // 获取当前鼠标位置
                        Point mousePoint = Mouse.GetPosition(e.Source as FrameworkElement);

                        FrmViewImage frm = new FrmViewImage(mousePoint.X + this.Top, mousePoint.Y + this.Left, this.getDocumentStringFormRichTextBox(this.txtQRCodeContent), ((BitmapSource)img.Source).CloneCurrentValue());
                        mFrmViewImageList.Add(frm);
                        frm.Show();

                        return;
                    }
                }

                mMouseDownDateTime = DateTime.Now;
            }
        }

        private void closeAllFrmViewImage()
        {
            foreach (var item in mFrmViewImageList)
            {
                item.Close();
            }
        }

        #endregion

        #region 解析粘贴板的图像

        ZXing.BarcodeReader mBarcodeReader { get; set; } = new ZXing.BarcodeReader();

        void BtnDecode_Click(object sender, RoutedEventArgs e)
        {
            System.Drawing.Bitmap bitmap = null;
            try
            {
                bitmap = Util.Drawing.DrawingUtils.BitmapSource2Bitmap(Clipboard.GetImage());
            }
            catch
            {
                return;
            }

            // 详细配置到源码的 common/CharacterSetECI.cs 中查看
            // mBarcodeReader.Options.CharacterSet = "UTF-8";
            // mBarcodeReader.Options.CharacterSet = "GB18030";

            if (cbCharacterSet.SelectedValue is ComboBoxItem cbi2)
            {
                try
                {
                    string tempEncoding = cbi2.Content.ToString();
                    mBarcodeReader.Options.CharacterSet = ZXing.Common.CharacterSetECI.getCharacterSetECIByName(tempEncoding).EncodingName;
                }
                catch (Exception ex)
                {
                    WPFControls.MessageBox.ShowErrorDialog(ex);
                    mBarcodeReader.Options.CharacterSet = null;
                }
            }       

            var r0 = mBarcodeReader.Decode(bitmap);
            if (r0 == null)
            {
                txtDecodeContent.Text = string.Empty;
                txtDecodeFormat.Text = string.Empty;

                WPFControls.MessageBox.ShowErrorDialog("图像不能被解析");
            }
            else
            {
                txtDecodeContent.Text = r0.Text;

                switch (r0.BarcodeFormat)
                {
                    case ZXing.BarcodeFormat.QR_CODE:
                        {
                            r0.ResultMetadata.TryGetValue(ZXing.ResultMetadataType.ERROR_CORRECTION_LEVEL, out object v);
                            txtDecodeFormat.Text = $"条码类型: {r0.BarcodeFormat.ToString()}; 纠错等级: {v.ToString()}";
                        }
                        break;
                    default:
                        {
                            txtDecodeFormat.Text = $"条码类型: {r0.BarcodeFormat.ToString()}";
                        }
                        break;
                }
            }
        }

        private void TxtDecodeContent_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            (sender as TextBox).SelectAll();
        }

        #endregion






        static double mMinWidth { get; set; } = -1d;

        #region [DP]IsExpand

        public static readonly DependencyProperty IsExpandProperty = DependencyProperty.Register
        (
            name: "IsExpand",
            propertyType: typeof(bool),
            ownerType: typeof(MainWindow),
            validateValueCallback: null, // new ValidateValueCallback((toValidate) => { return true; }),
            typeMetadata: new PropertyMetadata
            (
                defaultValue: false,
                propertyChangedCallback: onIsExpand_PropertyChangedCallback,
                coerceValueCallback: null
            )
        );

        public bool IsExpand
        {
            get { return (bool)GetValue(IsExpandProperty); }
            set { SetValue(IsExpandProperty, value); }
        }

        public static void onIsExpand_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is MainWindow target)
            {
                if (mMinWidth == -1)
                {
                    mMinWidth = target.Width;
                }
                else if (mMinWidth > target.Width)
                {
                    mMinWidth = target.Width;
                }

                if ((bool)e.NewValue)
                {
                    // target.Width = 1350d;
                    target.Width = 1400d;

                    // 动态设置 Grid Column, 让 splitter 能调整控件占比
                    target.splitter0.Visibility = Visibility.Visible;

                    target.g0.ColumnDefinitions.RemoveAt(0);
                    target.g0.ColumnDefinitions.RemoveAt(0);
                    target.g0.ColumnDefinitions.RemoveAt(0);

                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0.3, GridUnitType.Star) });
                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0.7, GridUnitType.Star) });
                }
                else
                {
                    target.Width = mMinWidth;

                    // 动态设置 Grid Column, 让 splitter 能调整控件占比
                    target.splitter0.Visibility = Visibility.Collapsed;

                    target.g0.ColumnDefinitions.RemoveAt(0);
                    target.g0.ColumnDefinitions.RemoveAt(0);
                    target.g0.ColumnDefinitions.RemoveAt(0);

                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
                    target.g0.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
                }
            }
        }

        #endregion

        #region 导入数据

        private void excelFile_ImportSuccess(object sender, EventArgs e)
        {
            var ds = Util.Excel.ExcelUtils_Aspose.Excel2DataSetAsString
            (
                path: ucsfExcelFile.FileName,
                exportColumnName: true
            );

            tabCtrl.Items.Clear();

            for (int i = 0; i < ds.Tables.Count; i++)
            {
                TabItem toAdd = new TabItem();
                toAdd.Header = ds.Tables[i].TableName;

                Grid grid = new Grid();

                Client.Components.StandardDataGridView dgv = new Client.Components.StandardDataGridView();
                dgv.Description = $"共 {ds.Tables[i].Rows.Count} 条数据";
                grid.Children.Add(dgv);

                DataGrid dg = dgv.DataGrid;
                dg.AutoGenerateColumns = true;
                dg.IsReadOnly = true;
                dg.CanUserSortColumns = true;

                dg.CanUserAddRows = false;
                dg.CanUserDeleteRows = false;
                dg.SelectionMode = DataGridSelectionMode.Extended;
                dg.SelectionUnit = DataGridSelectionUnit.CellOrRowHeader;

                dg.SelectedCellsChanged += Dg_SelectedCellsChanged;

                dg.ItemsSource = ds.Tables[i].DefaultView; // 简化以上获取 DataView 的方式

                toAdd.Content = grid;

                tabCtrl.Items.Add(toAdd);
            }

            tabCtrl.SelectedIndex = -1;

            if (tabCtrl.Items.Count > 0)
            {
                tabCtrl.SelectedIndex = 0;
            }
        }

        //private void Dg_MouseUp(object sender, MouseButtonEventArgs e)
        //{
        //    System.Diagnostics.Debug.WriteLine("Dg_MouseUp");

        //    if (e.ChangedButton == MouseButton.Left)
        //    {
        //        DataGrid target = sender as DataGrid;
        //        changed(target);
        //    }
        //}

        private void Dg_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Dg_SelectedCellsChanged");

            if (e.AddedCells != null && e.AddedCells.Count > 0)
            {
                DataGrid target = sender as DataGrid;
                changed(target);
            }
        }

        void changed(DataGrid target)
        {
            string s = @"|";
            StringBuilder sb = new StringBuilder();

            // DataGrid target = sender as DataGrid;
            for (int index = 0; index < target.SelectedCells.Count; index++)
            {
                var item = target.SelectedCells[index];
                string msg = GetCellValue(item)?.ToString(); // 这里有 null 时会报错, 需要添加 ? 进行处理
                sb.Append(msg);
                if (target.SelectedCells.Count > 1)
                {
                    sb.Append(s);
                }
            }

            var textRange = new TextRange(this.txtQRCodeContent.Document.ContentStart, this.txtQRCodeContent.Document.ContentEnd);
            textRange.Text = sb.ToString();
            drawQRCode();

            MyTcpServer_Send(sb.ToString());
        }


        public object GetCellValue(DataGridCellInfo dataGridCellInfo)
        {
            object r = null;

            if (dataGridCellInfo != null)
            {
                DataGridBoundColumn column = dataGridCellInfo.Column as DataGridBoundColumn;
                if (column != null)
                {
                    FrameworkElement element = new FrameworkElement()
                    {
                        DataContext = dataGridCellInfo.Item
                    };

                    BindingOperations.SetBinding
                    (
                        target: element,
                        dp: FrameworkElement.TagProperty,
                        binding: column.Binding
                    );

                    r = element.Tag;
                }
            }

            return r;
        }

        void FromClipboard()
        {
            string clipboardText = Clipboard.GetText();
            string[] splitArr = clipboardText.Split('\n');
            FromClipboard_WhatUWant(splitArr);
        }

        void FromClipboard2()
        {
            // 拆分出 FromClipboard 和 FromClipboard2 的原因
            // 泰科的项目中, 他的二维码很奇特, 内容 : TE\nPA1B\n292243-2\nF322264\n30000\n3
            // 用 FromClipboard 的方法会拆分出 多段, 所以多出来一个方法, 其实更好的方法是让使用者去配置拆分的参数
            string clipboardText = Clipboard.GetText();
            string[] splitArr = clipboardText.Split(new string[1] { "\r\n" }, StringSplitOptions.None);
            FromClipboard_WhatUWant(splitArr);
        }

        void FromClipboard_WhatUWant(string[] splitArr)
        {
            DataTable dt = new DataTable("来自粘贴板");
            int maxColumn = 1;
            dt.Columns.Add($"Column{maxColumn}");

            tabCtrl.Items.Clear();

            foreach (var line in splitArr)
            {
                var trimedLine = line.Trim();

                if (trimedLine.IsNullOrWhiteSpace())
                {
                    continue;
                }

                var toAddd = dt.NewRow();
                dt.Rows.Add(toAddd);

                var arr = trimedLine.Split('\t');
                if (arr.Length > maxColumn)
                {
                    maxColumn = arr.Length;
                    calcMaxColumn(dt, arr.Length); // 存在问题                
                }

                for (int index = 0; index < arr.Length; index++)
                {
                    toAddd[index] = arr[index];
                }
            }

            TabItem toAdd = new TabItem();
            toAdd.Header = dt.TableName;

            Grid grid = new Grid();

            Client.Components.StandardDataGridView dgv = new Client.Components.StandardDataGridView();
            dgv.Description = $"共 {dt.Rows.Count} 条数据";
            grid.Children.Add(dgv);

            DataGrid dg = dgv.DataGrid;

            dg.AutoGenerateColumns = true;
            dg.IsReadOnly = true;
            dg.CanUserSortColumns = true;

            dg.CanUserAddRows = false;
            dg.CanUserDeleteRows = false;
            dg.SelectionMode = DataGridSelectionMode.Extended;
            dg.SelectionUnit = DataGridSelectionUnit.CellOrRowHeader;

            dg.SelectedCellsChanged += Dg_SelectedCellsChanged;

            dg.ItemsSource = dt.DefaultView; // 简化以上获取 DataView 的方式            

            toAdd.Content = grid;

            tabCtrl.Items.Add(toAdd);

            tabCtrl.SelectedIndex = -1;

            if (tabCtrl.Items.Count > 0)
            {
                tabCtrl.SelectedIndex = 0;
            }
        }

        void calcMaxColumn(DataTable dt, int maxColumn)
        {
            if (dt.Columns.Count >= maxColumn)
            {
                return;
            }

            for (int i = dt.Columns.Count; i < maxColumn; i++)
            {
                dt.Columns.Add($"Column{i + 1}");
            }
        }

        #endregion

        #region FakeScanner Server - 需要配合 FakeScanner.apk 使用

        private int _MyTcpServerPort = 48001;
        public int MyTcpServerPort
        {
            get { return _MyTcpServerPort; }
            set
            {
                if (value < 0 || value > 65535)
                {
                    return;
                }

                _MyTcpServerPort = value;
                this.OnPropertyChanged(nameof(MyTcpServerPort));

                if (value > 0)
                {
                    DebonceAction_Port.Debounce
                    (
                        interval: 500,
                        action: () =>
                        {
                            App.BeginInvokeOnMainThread(updateQRCode);
                        },
                        syncInvoke: null
                    );
                }
            }
        }

        public Util.Web.MyTcpServer mMyTcpServer { get; set; }

        Util.ActionUtils.DebounceAction DebonceAction_Port { get; set; } = new Util.ActionUtils.DebounceAction();

        void MyTcpServer_Open_Click(object sender, RoutedEventArgs e)
        {
            if (mMyTcpServer != null)
            {
                WPFControls.MessageBox.ShowError("服务已开启");
                return;
            }

            mMyTcpServer = new Util.Web.MyTcpServer(isStandardReceive: true);

            mMyTcpServer.StatusChange += new EventHandler<Util.Web.TcpXxxStatusChangeEventArgs>(myTcpServer_StatusChange_Handle);
            mMyTcpServer.StartServer(System.Net.IPAddress.Any, this.MyTcpServerPort); // 为所有本机所有 ip 开启

            System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
            updateQRCode();
        }

        void myTcpServer_StatusChange_Handle(object sender, Util.Web.TcpXxxStatusChangeEventArgs args)
        {
            App.BeginInvokeOnMainThread(new Action(() =>
            {
                frmTcpServerConsole.ucConsole.Add(new Util.Model.ConsoleData(args.ConsoleMsg, (Util.Model.ConsoleMsgType)args.ConsoleMsgType, args.EntryTime));
            }));
        }

        void MyTcpServer_Close_Click(object sender, RoutedEventArgs e)
        {
            mMyTcpServer?.StopServer();

            // 为了接收成功关闭的状态信息, 不注销事件的订阅
            // mMyTcpServer.StatusChange -= new EventHandler<Util.Web.TcpXxxStatusChangeEventArgs>(myTcpServer_StatusChange_Handle);

            mMyTcpServer = null;

            updateQRCode();
        }

        void MyTcpServer_Send(string content)
        {
            if (mMyTcpServer == null || mMyTcpServer.IsServerStart == false)
            {
                // WPFControls.MessageBox.ShowErrorDialog("发送失败。服务未启动");
                return;
            }
            
            // mMyTcpServer.StandardSend(content); // 改此方式  能连通 COM 口
            mMyTcpServer.StandardSend(content);
        }

        FrmMyTcpServerConsole frmTcpServerConsole { get; set; } = new FrmMyTcpServerConsole();

        void MyTcpServer_ShowConsole_Click(object sender, RoutedEventArgs e)
        {
            frmTcpServerConsole.Show();

            if (frmTcpServerConsole.WindowState == WindowState.Minimized)
            {
                frmTcpServerConsole.WindowState = WindowState.Normal;
            }

            frmTcpServerConsole.Activate();
        }

        void CMD_Method_MyTcpServerSend()
        {
            string content = this.getDocumentStringFormRichTextBox(this.txtQRCodeContent);
            MyTcpServer_Send(content);
        }

        // 测试过 在虚拟机上是无法 填写 192.168.18.1 进行连接, 所以无需获取虚拟网卡
        private List<Howe.Helper.NetworkInterfaceAdv> _NetworkInterfaceAdvList;
        public List<Howe.Helper.NetworkInterfaceAdv> NetworkInterfaceAdvList
        {
            get { return _NetworkInterfaceAdvList; }
            set
            {
                _NetworkInterfaceAdvList = value;
                this.OnPropertyChanged(nameof(NetworkInterfaceAdvList));

                if (value != null && value.Count > 0)
                {
                    SelectedNetworkInterfaceAdv = value[0];
                }
            }
        }

        private Howe.Helper.NetworkInterfaceAdv _SelectedNetworkInterfaceAdv;
        public Howe.Helper.NetworkInterfaceAdv SelectedNetworkInterfaceAdv
        {
            get { return _SelectedNetworkInterfaceAdv; }
            set
            {
                if (value != null && value.Name == Howe.Helper.NetworkInterfaceAdv.RefleshInfo)
                {
                    NetworkInterfaceAdvList = Howe.Helper.WinNetworkHelper.GetAllDevice();
                    return;
                }

                _SelectedNetworkInterfaceAdv = value;
                this.OnPropertyChanged(nameof(SelectedNetworkInterfaceAdv));

                updateQRCode();
            }
        }

        #endregion

        #region 二维码

        private string _MyTcpServerQRCodeContent;
        public string MyTcpServerQRCodeContent
        {
            get { return _MyTcpServerQRCodeContent; }
            set
            {
                _MyTcpServerQRCodeContent = value;
                this.OnPropertyChanged(nameof(MyTcpServerQRCodeContent));
            }
        }


        private BitmapSource _MyTcpServerQRCode;
        public BitmapSource MyTcpServerQRCode
        {
            get { return _MyTcpServerQRCode; }
            set
            {
                _MyTcpServerQRCode = value;
                this.OnPropertyChanged(nameof(MyTcpServerQRCode));
            }
        }


        private void QRCodeButton_Click(object sender, RoutedEventArgs e)
        {
            updateQRCode();
        }

        void updateQRCode()
        {
            if (mMyTcpServer == null || mMyTcpServer.IsServerStart == false)
            {
                MyTcpServerQRCodeContent = string.Empty;
                MyTcpServerQRCode = null;
                return;
            }

            string temp = "{\"IP\":\"" + SelectedNetworkInterfaceAdv.IPAddress + "\",\"Port\":" + MyTcpServerPort + "\"}";

            if (MyTcpServerQRCodeContent != temp)
            {
                MyTcpServerQRCodeContent = temp;
                MyTcpServerQRCode = zxingDrawQRCode(temp);
            }
        }

        public System.Windows.Controls.Primitives.CustomPopupPlacement[] placePopup
        (
            Size popupSize,
            Size targetSize,
            Point offset
        )
        {
            System.Windows.Controls.Primitives.CustomPopupPlacement placement1 =
               new System.Windows.Controls.Primitives.CustomPopupPlacement(new Point(0, 30), System.Windows.Controls.Primitives.PopupPrimaryAxis.Vertical);

            System.Windows.Controls.Primitives.CustomPopupPlacement placement2 =
                new System.Windows.Controls.Primitives.CustomPopupPlacement(new Point(10, 20), System.Windows.Controls.Primitives.PopupPrimaryAxis.Horizontal);

            System.Windows.Controls.Primitives.CustomPopupPlacement[] ttplaces =
                    new System.Windows.Controls.Primitives.CustomPopupPlacement[] { placement1, placement2 };

            return ttplaces;
        }

        #endregion

    }
}
